package com.dc.handler.haiwanalarm;

import cn.hutool.core.codec.BCD;
import cn.hutool.json.JSONObject;
import com.dc.enums.HaiWanAlarm;
import com.dc.enums.HaiWanType;
import com.dc.util.ByteUtil;
import com.dc.util.CRC;
import com.intelligt.modbus.jlibmodbus.serial.SerialPort;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhang
 */
@Slf4j
@Component
public class ModbusRtuMaster {

    /**
     * 检测系统中可用的通讯端口类
     */
    private static SerialPortWrapperImpl wrapper;

    private static ModbusMaster master;

    private static ModbusFactory modbusFactory;

    public ModbusRtuMaster modbusRtuMaster;

    public ModbusRtuMaster() {
    }

    /**
     * 端口是否在使用
     *
     * @param serialNumber
     * @return
     */
    public boolean inUse(String serialNumber) {
        boolean bl = false;
        if (wrapper == null) {
            return bl;
        }
        if (wrapper.getPort() != null && wrapper.getPort().equalsIgnoreCase(serialNumber)) {
            bl = master.isInitialized();
        }
        return bl;
    }

    /**
     * 初始化创建 主机master 连接
     *
     * @param serialNumber
     * @param baudRate
     * @param dataBit
     * @param stopBit
     * @param checkoutBit
     * @throws Exception
     */
    public void createRtuMaster(String serialNumber, int baudRate, int dataBit, int stopBit, int checkoutBit) throws Exception {//relayParamConfig.getSerialNumber(), relayParamConfig.getBaudRate(),
        // relayParamConfig.getDataBit(),relayParamConfig.getStopBit(), relayParamConfig.getCheckoutBit()
        // 设置串口参数，串口是COM1，波特率是9600
        wrapper = new SerialPortWrapperImpl(serialNumber, baudRate,
                dataBit, stopBit, checkoutBit, 0, 0);
        //SerialPort.PARITY_NONE
        modbusFactory = new ModbusFactory();
        // System.out.println("relayParamConfig.getSerialNumber()->"+relayParamConfig.getSerialNumber());
        master = modbusFactory.createRtuMaster(wrapper);
        master.init();
    }

    public void closeRtuMaster() throws Exception {
        wrapper.close();
    }

    /**
     * 写入数据
     *
     * @param slaveId 从机
     * @param offset  位置
     * @param value   值
     * @throws Exception
     */
    public void controlRelay(int slaveId, int offset, int value) throws Exception {
        writeRegister(master, slaveId, offset, value);
    }

    /**
     * 读取数据
     *
     * @param master  主机
     * @param slaveId 从机id
     * @param start   开始位置
     * @param len     一共读多少个
     * @throws Exception
     */
    private Object[] readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) throws Exception {
        ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
        ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
        Object[] result = new Object[2];
        if (response.isException()) {
            System.err.println("读取保持寄存器错误，错误信息是" + response.getExceptionMessage());
            return null;
        }
        // 详细数据
        result[0] = response.getData();
        // 精确数据
        result[1] = response.getShortData();
        return result;
    }

    /**
     * 写入数据
     *
     * @param master
     * @param slaveId
     * @param offset
     * @param value
     * @throws Exception
     */
    private void writeRegister(ModbusMaster master, int slaveId, int offset, int value) throws Exception {
        WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);
        ByteQueue queue = new ByteQueue();
        WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
        if (response.isException()) {
            System.out.println("写保持寄存器错误，错误信息是" + response.getExceptionMessage());
        } else {
            System.out.println("指令发送成功!");
        }
    }

    /**
     * 初始化 创建连接
     */
    @PostConstruct
    public void init() {
        ModbusRtuMaster rmt = new ModbusRtuMaster();
        try {
            rmt.createRtuMaster("COM1",
                    SerialPort.BaudRate.BAUD_RATE_9600.getValue(),
                    8,
                    1,
                    SerialPort.Parity.NONE.getValue());
        } catch (Exception e) {
            log.info("海湾设备初始化失败,请检查服务是否开启:{}", e.getMessage());
        }
        modbusRtuMaster = rmt;
    }

    /**
     * 开始请求
     *
     * @return
     */
    public JSONObject startRequest() {
        JSONObject json = new JSONObject();
        try {
            // 读取数据
            // 查询 从机id为1的 起始位置40000开始，读取4位（40001-40004）
            Object[] objects = modbusRtuMaster.readHoldingRegisters(master, 1, 40001, 4);
            if (null != objects) {
                // 如果校验不通过，重新发送请求
                if (!isFault(objects)) {
                    json = getEventsSecond(objects);
                } else {
                    // 再次进行请求 （40005-40008）
                    Object[] objectsSecond = modbusRtuMaster.readHoldingRegisters(master, 1, 40005, 4);
                    if (!isFault(objectsSecond)) {
                        getEvents(objects);
                    }
                }
            }
        } catch (Exception e) {
            log.info("海湾=功能码3请求数据失败：{}", e.getMessage());
        }
        return json;
    }


    public static void main(String[] args) {
        ModbusRtuMaster rmt = new ModbusRtuMaster();
        try {
            rmt.createRtuMaster("COM1",
                    SerialPort.BaudRate.BAUD_RATE_9600.getValue(),
                    8,
                    1,
                    SerialPort.Parity.NONE.getValue());

            // 写入寄存器
            // rmt.controlRelay(1, 40001, 1122);

            // 举例
            // 000916-Rx:01 03 9C 41 00 04 3A 4D
            // 000917-Tx:01 03 08 00 0A 00 0C 00 04 00 0C 6E 12

            // 读取数据
            // 查询 从机id为1的 起始位置40000开始，读取4位（40001-40004）
            Object[] objects = rmt.readHoldingRegisters(master, 1, 40001, 4);
            if (null != objects) {
                // 如果校验不通过，重新发送请求
                if (!isFault(objects)) {
                    getEventsSecond(objects);
                } else {
                    // 再次进行请求 （40005-40008）
                    Object[] objectsSecond = rmt.readHoldingRegisters(master, 1, 40005, 4);
                    if (!isFault(objectsSecond)) {
                        getEvents(objects);
                    }
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                rmt.closeRtuMaster();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static JSONObject getEventsSecond(Object[] objects) {

        JSONObject jsonObject = new JSONObject();

        // 拿到详细数据
        short[] bytes = (short[]) objects[1];
        System.out.println("数据2：" + Arrays.toString(bytes));

        // 400001
        String[] toHexString_400001 = changeUpdate(Integer.toHexString(bytes[0]));
        log.info("40001寄存器高字节-事件类型:{}", getEventMap().get(toHexString_400001[0]));
        jsonObject.put("deviceStatus", HaiWanAlarm.getValues(toHexString_400001[0]));
        // 消防电源状态监控系统通道描述，见附录1
        log.info("40001寄存器低字节:{}", toHexString_400001[1]);

        String[] toHexString_400002 = changeUpdate(Integer.toHexString(bytes[1]));
        log.info("40002寄存器高字节-设备类型:{}", toHexString_400002[0]);
        jsonObject.put("deviceType", HaiWanType.getDesc(toHexString_400002[0]));
        log.info("40002寄存器低字节:{}", toHexString_400002[1]);

        String[] toHexString_400003 = changeUpdate(Integer.toHexString(bytes[2]));
        log.info("40003寄存器高字节:{}", toHexString_400003[0]);
        String address = toHexString_400002[1] + toHexString_400003[0];
        // 字符串转BCD码
        byte[] byteAddress = BCD.strToBcd(address);
        log.info("控制器主机网络地址0000~0255(压缩BCD码)之后:{}", Arrays.toString(byteAddress));

        log.info("40003寄存器低字节:{}", toHexString_400003[1]);
        // 字符串转BCD码
        byte[] byteLoop = BCD.strToBcd(toHexString_400003[1]);
        log.info("回路号00~99(压缩BCD码)之后:{}", Arrays.toString(byteLoop));

        // 字符串转BCD码
        byte[] byteDeviceId = BCD.strToBcd(bytes[3] + "");
        log.info("40004寄存器=设备编码0000~0255(压缩BCD码):{},bcd之后:{}", bytes[3], byteDeviceId);

        // 模拟多个设备 Instant.now().toEpochMilli()
        jsonObject.put("serialNumber", "20221107174000");
        /*byte[] bytes1 = BCD.strToBcd("33");
        System.err.println(Arrays.toString(bytes1));*/
        return jsonObject;
    }


    private static void getEventsNew(Object[] objects) {
        // 拿到详细数据
        short[] bytes = (short[]) objects[1];
        System.out.println("数据2：" + Arrays.toString(bytes));

        // 400001
        String[] toHexString_400001 = changeUpdate(Integer.toHexString(bytes[0]));
        log.info("40001寄存器高字节-事件类型:{}", getEventMap().get(toHexString_400001[0]));
        // 消防电源状态监控系统通道描述，见附录1
        log.info("40001寄存器低字节:{}", toHexString_400001[1]);

        String[] toHexString_400002 = changeUpdate(Integer.toHexString(bytes[1]));
        log.info("40002寄存器高字节-设备类型:{}", toHexString_400002[0]);
        log.info("40002寄存器低字节:{}", toHexString_400002[1]);

        String[] toHexString_400003 = changeUpdate(Integer.toHexString(bytes[2]));
        log.info("40003寄存器高字节:{}", toHexString_400003[0]);
        String address = toHexString_400002[1] + toHexString_400003[0];
        // 字符串转BCD码
        byte[] byteAddress = BCD.strToBcd(address);
        log.info("控制器主机网络地址0000~0255(压缩BCD码)之后:{}", Arrays.toString(byteAddress));

        log.info("40003寄存器低字节:{}", toHexString_400003[1]);
        // 字符串转BCD码
        byte[] byteLoop = BCD.strToBcd(toHexString_400003[1]);
        log.info("回路号00~99(压缩BCD码)之后:{}", Arrays.toString(byteLoop));

        String[] toHexString_400004 = changeUpdate(Integer.toHexString(bytes[3]));
        String deviceId = toHexString_400004[0] + toHexString_400004[1];
        // 字符串转BCD码
        byte[] byteDeviceId = BCD.strToBcd(deviceId);
        log.info("40004寄存器高字节:{}", toHexString_400004[0]);
        log.info("40004寄存器低字节:{}", toHexString_400004[1]);
        log.info("设备编码0000~0255(压缩BCD码):{},bcd之后:{}", deviceId, byteDeviceId);

        byte[] bytes1 = BCD.strToBcd("0211");
        System.err.println(Arrays.toString(bytes1));
    }

    private static String[] changeUpdate(String value) {
        if (value.length() == 1) {
            value = "000" + value;
        }
        if (value.length() == 2) {
            value = "00" + value;
        }
        if (value.length() == 3) {
            value = "0" + value;
        }
        String temp = value.substring(0, 2) + "," + value.substring(2);
        return temp.split(",");
    }

    /**
     * 控制器发送事件
     *
     * @param objects 请求返回数据
     */
    private static void getEvents(Object[] objects) {
        // 拿到详细数据
        byte[] bytes = (byte[]) objects[0];

        log.info("40001寄存器高字节-事件类型:{}", getEventMap().get(numFormat(bytes[0])));
        // 消防电源状态监控系统通道描述，见附录1
        log.info("40001寄存器低字节:{}", numFormat(bytes[1]));
        log.info("40002寄存器高字节-设备类型:{}", numFormat(bytes[2]));

        log.info("40002寄存器低字节:{}", numFormat(bytes[3]));
        log.info("40003寄存器高字节:{}", numFormat(bytes[4]));
        String address = ByteUtil.decimalToBinary(Integer.valueOf(measures(bytes[3]) + measures(bytes[4])), 12);
        log.info("控制器主机网络地址0000~0255(压缩BCD码)之后:{}", address);

        log.info("40003寄存器低字节:{}", measures(bytes[5]));
        String loop = ByteUtil.decimalToBinary(Integer.valueOf(measures(bytes[5])), 8);
        log.info("回路号00~99(压缩BCD码)之后:{}", loop);

        log.info("40004寄存器高字节:{}", measures(bytes[6]));
        log.info("40004寄存器低字节:{}", measures(bytes[7]));
        Integer integer = Integer.valueOf(measures(bytes[6]) + measures(bytes[7]));
        // 设备编码
        String deviceId = ByteUtil.decimalToBinary(integer, 12);
        log.info("设备编码0000~0255(压缩BCD码):{},bcd之后:{}", integer, deviceId);

        byte[] bytes1 = BCD.strToBcd("0055");
        System.err.println(Arrays.toString(bytes1));

        byte[] bb = new byte[]{0, 85};
        System.out.println(BCD.bcdToStr(bb));

    }

    private static String numFormat(byte num) {
        return String.format("%02x", num);
    }

    /**
     * 措施
     *
     * @return
     */
    private static String measures(byte num) {
        return Integer.toHexString(Integer.valueOf(num));
    }

    /**
     * 判断数据是否异常，出错
     * array[3] = (byte) 0x00;
     * array[4] = (byte) 0x0A;
     * array[5] = (byte) 0x00;
     * array[6] = (byte) 0x0C;
     * array[7] = (byte) 0x00;
     * array[8] = (byte) 0x04;
     * array[9] = (byte) 0x00;
     * array[10] = (byte) 0x0C;
     *
     * @return
     */
    private static boolean isFault(Object[] objects) {
        // false 无异常,true 有异常
        boolean flag = false;

        // 拿到详细数据
        byte[] bytes = (byte[]) objects[0];
        System.out.println("数据：" + Arrays.toString(bytes));

        byte[] array = new byte[11];
        // 前面部分，从机，功能码，数量
        array[0] = (byte) 0x01;
        array[1] = (byte) 0x03;
        array[2] = (byte) 0x08;
        // CRC校验累加,从第三位开始
        int temp = 3;
        for (int i = temp; i < bytes.length + temp; i++) {
            array[i] = bytes[(i - temp)];
        }
        String crc = CRC.getCRC(array);
        // 高字节、低字节互换
        String result = crc.substring(2) + crc.substring(0, 2);
        System.out.println("校验码:" + result);
        if (1 != 1) {
            flag = true;
        }
        return flag;
    }

    /**
     * 事件信息
     *
     * @return
     */
    private static Map<String, String> getEventMap() {
        return new HashMap<String, String>(4) {{
            put("00", "正常");
            put("01", "火警");
            put("02", "故障");
            put("07", "监管");
        }};
    }

}
